﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;


namespace Checkpoint2XNA.Models
{
    public class Cylinder : Model3D
    {
        public Vector3 Bottom { get; set; }
        public Vector3 Top { get; set; }
        public float Radius { get; set; }
        public float Height { get; set; }

        public Cylinder(Vector3 p, float r, float h, Color c)
            : base()
        {
            Bottom = p;
            Top = new Vector3(p.X, p.Y + h, p.Z);
            Radius = r;
            Height = h;
            Color = c;
        }

        public override void MoveTo(Vector3 p)
        {
            Bottom = p;
            Top = new Vector3(p.X, p.Y + Height, p.Z);
        }

        public override Color  GetColorAt(Vector3 point)
        {
 	        return Color;
        }

        public override Vector3 GetNormal(Vector3 point)
        {
            if (point.Y >= Top.Y - 1E-6)
            {
                return new Vector3(0, 1, 0);
            }
            //forget the bottom, no one will see it
            else
            {
                var n = new Vector3(point.X - Top.X,
                                    0,
                                    point.Z - Top.Z);

                return n;
            }
        }

        public override double RayCollide(Vector3 origin, Vector3 ray)
        {
            double t = -1;
             //int IntersectSegmentCylinder(Point sa, Point sb, Point p, Point q, float r, float &t)    
            Vector3 m = origin - Bottom;
            Vector3 d = Top - Bottom;
            float md = Vector3.Dot(m, d);
            float nd = Vector3.Dot(ray, d);
            float dd = Vector3.Dot(d, d);
            // Test if segment fully outside either endcap of cylinder
            //if (md < 0.0f && md + nd < 0.0f) return 0; // Segment outside ‘p’ side of cylinder
            //if (md > dd && md + nd > dd) return 0;     // Segment outside ‘q’ side of cylinder
            float nn = Vector3.Dot(ray, ray);
            float mn = Vector3.Dot(m, ray);
            float a = dd * nn - nd * nd;
            float k = Vector3.Dot(m, m) - Radius * Radius;
            float c = dd * k - md * md;
            if (Math.Abs(a) < 1E-6) 
            {
                // Segment runs parallel to cylinder axis
                if (c > 0.0f) return 0; // ‘a’ and thus the segment lie outside cylinder
                // Now known that segment intersects cylinder; figure out how it intersects
                if (md < 0.0f) t = -mn / nn; // Intersect segment against ‘p’ endcap
                else if (md > dd) t = (nd - mn) / nn; // Intersect segment against ‘q’ endcap
                else t = 0.0f; // ‘a’ lies inside cylinder
                return 1;
            }
            float b = dd * mn - nd * md;
            float discr = b * b - a * c;
            if (discr < 0.0f) return 0; // No real roots; no intersection
            t = (-b - Math.Sqrt(discr)) / a;
            //if (t < 0.0f || t > 1.0f) return 0; // Intersection lies outside segment
            if (md + t * nd < 0.0f) {
                // Intersection outside cylinder on ‘p’ side
                if (nd <= 0.0f) return 0; // Segment pointing away from endcap
                t = -md / nd;
                // Keep intersection if Vector3.Dot(S(t) - p, S(t) - p) <= r^2
                if( k + 2 * t * (mn + t * nn) <= 0.0f)
                {
                    return t;
                }
                else
                {
                    return -1;
                }
            } else if (md + t * nd > dd) {
                // Intersection outside cylinder on ‘q’ side
                if (nd >= 0.0f) return 0; // Segment pointing away from endcap
                t = (dd - md) / nd;
                // Keep intersection if Vector3.Dot(S(t) - q, S(t) - q) <= r^2
                if( k + dd - 2 * md + t * (2 * (mn - nd) + t * nn) <= 0.0f)
                {
                    return t;
                }
                else
                    return -1;
            }
            // Segment intersects cylinder between the end-caps; t is correct
            return t;    
        }
    }
}
